﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GoodTrader.GTSystem
{
    public class CTraderSystem
    {
        /*
         * 매수와 매도 전용 신호를 분리해서 운영한다.
         * 추후에 양계좌를 위해서도 그렇고, 이익 계산에 편하기 때문이다.
         */ 
        // 매수 전용신호
        protected GTSignal.CSignalManager m_BuySignalManager = new GTSignal.CSignalManager();
        public GTSignal.CSignalManager BuySignalManager
        {
            get { return m_BuySignalManager; }
            set { m_BuySignalManager = value; }
        }
        // 매도 전용신호
        protected GTSignal.CSignalManager m_SellSignalManager = new GTSignal.CSignalManager();
        public GTSignal.CSignalManager SellSignalManager
        {
            get { return m_SellSignalManager; }
            set { m_SellSignalManager = value; }
        }

        protected void InitBuySignalList()
        {
            m_BuySignalManager.ClearSignalList();
        }

        protected void InitSellSignalList()
        {
            m_SellSignalManager.ClearSignalList();
        }

        // 신호 관리자 - 신호를 관리한다.
        protected GTSignal.CSignalManager m_SignalManager = new GTSignal.CSignalManager();
        public GTSignal.CSignalManager SignalManager
        {
            get { return m_SignalManager; }
            set { m_SignalManager = value; }
        }
        // 계좌 관리자 - 계좌에 주문을 연결 시킨다.
        protected GTOrder.CAccountManager m_AccountManager = new GTOrder.CAccountManager();
        /*
         * @brief : 매수 신호를 발생시킨다.
         * @param a_DayIndex : 신호가 발생한 봉의 일일 인덱스 번호 - 그날의 첫봉이 0이다.
         * @param a_Time : 신호가 발생한 시간
         * @param a_SignalName : 신호 이름
         * @param a_Code : 종목 코드
         * @param a_Price : 가격
         * @param a_OrderAmount : 주문 수량
         * @param a_OrderMode : 주문 모드 
         * @param a_OrderCond : 주문 조건
         */ 
        public void Buy(int a_ConstIndex, int a_DayIndex, double a_Time, string a_SignalName, string a_Code, double a_Price, int a_OrderAmount, string a_OrderMode, string a_OrderCond)
        {
            // 주문 요청을 한다.
            RequestOrder(GTSignalType.BUY, a_ConstIndex, a_DayIndex, a_Time, a_SignalName, a_Code, a_Price, a_OrderAmount, a_OrderMode, a_OrderCond);
        }

        public void Sell(int a_ConstIndex, int a_DayIndex, double a_Time, string a_SignalName, string a_Code, double a_Price, int a_OrderAmount, string a_OrderMode, string a_OrderCond)
        {
            // 주문 요청을 한다.
            RequestOrder(GTSignalType.SELL, a_ConstIndex, a_DayIndex, a_Time, a_SignalName, a_Code, a_Price, a_OrderAmount, a_OrderMode, a_OrderCond);
        }

        public void ExitSell(int a_ConstIndex, int a_DayIndex, double a_Time, string a_SignalName, string a_Code, double a_Price, int a_OrderAmount, string a_OrderMode, string a_OrderCond)
        {
            // 주문 요청을 한다.
            RequestOrder(GTSignalType.EXITSELL, a_ConstIndex, a_DayIndex, a_Time, a_SignalName, a_Code, a_Price, a_OrderAmount, a_OrderMode, a_OrderCond);
        }

        public void ExitBuy(int a_ConstIndex, int a_DayIndex, double a_Time, string a_SignalName, string a_Code, double a_Price, int a_OrderAmount, string a_OrderMode, string a_OrderCond)
        {
            // 주문 요청을 한다.
            RequestOrder(GTSignalType.EXITBUY, a_ConstIndex, a_DayIndex, a_Time, a_SignalName, a_Code, a_Price, a_OrderAmount, a_OrderMode, a_OrderCond);
        }

        public void ExitAll(int a_ConstIndex, int a_DayIndex, double a_Time, string a_SignalName, string a_Code, double a_Price, int a_OrderAmount, string a_OrderMode, string a_OrderCond)
        {
            // 주문 요청을 한다.
            RequestOrder(GTSignalType.EXITALL, a_ConstIndex, a_DayIndex, a_Time, a_SignalName, a_Code, a_Price, a_OrderAmount, a_OrderMode, a_OrderCond);
        }

        private void RequestOrder(GTSignalType a_Type, int a_ConstIndex, int a_DayIndex, double a_Time, string a_SignalName, string a_Code, double a_Price, int a_OrderAmount, string a_OrderMode, string a_OrderCond)
        {
            // 새로운 신호를 생성한다.
            GTSignal.CSignal newSignal = new GTSignal.CSignal(a_SignalName);
            newSignal.ConstIndex = a_ConstIndex;
            // 신호 타입을 넣어준다.
            newSignal.Type = a_Type;
            // 신호가 발생한 시간을 넣어준다.
            newSignal.SignalTime = a_Time;
            // 신호가 발생한 봉의 일일 인덱스를 넣어준다.
            newSignal.DayIndex = a_DayIndex;
            // 신호가 발생한 가격
            newSignal.Price = a_Price;
            // 신호에 요청한 주문개수
            newSignal.Amount = a_OrderAmount;

            string ticks = DateTime.Now.Ticks.ToString();
            // 틱수를 아이디로 활용한다.
            newSignal.ID = ticks;

            if (a_Type == GTSignalType.BUY || a_Type == GTSignalType.EXITBUY)
            {
                // 신호 관리자에 신호를 추가한다.
                m_BuySignalManager.AddSignal(newSignal);
            }
            else
            {
                m_SellSignalManager.AddSignal(newSignal);
            }

            GTOrder.COrderParameter orderParam = new GTOrder.COrderParameter();
            // 주문할 상품코드
            orderParam.Code = a_Code;
            // 주문 수량
            orderParam.Amount = a_OrderAmount;
            // 주문 가격
            orderParam.Price = a_Price;
            // 주문 타입
            orderParam.OrderType = "2"; // 매수
            // 주문 모드 : 시장가 혹은 지정가 등등
            orderParam.OrderMode = a_OrderMode;
            // 주문 조건
            orderParam.OrderCond = a_OrderCond;
            // 주문 시간
            orderParam.RequestedTime = a_Time;
            // 주문 일일 봉 인덱스
            orderParam.DayIndex = a_DayIndex;

            m_AccountManager.RequestOrder(orderParam);
        }

        public double CalcTotalSum()
        {
            double buySum = CalcBuySignal();
            double sellSum = CalcSellSignal();

            return buySum + sellSum;
        }

        /*
         * @brief : 매수 신호에 대한 손익을 계산합니다. 
         */ 
        private double CalcBuySignal()
        {
            double profitSum = 0.0;
            if (m_BuySignalManager.SignalList.Count < 2)
                return profitSum;

            // 누적 개수
            int accNum = 0;
            // 평균 단가
            double aveVal = 0.0;
            for(int i = 0; i < m_BuySignalManager.SignalList.Count; i++)
            {
                GTSignal.CSignal curSignal = m_BuySignalManager.SignalList[i];
                if (curSignal.Type == GTSignalType.BUY)
                {
                    // 매수일 때는 아직 청산이 이루어지지 않았으므로 수익을 알 수 없다.
                    double preAveVal = aveVal;
                    int preAccNum = accNum;
                    accNum += curSignal.Amount;
                    double priceSum = preAveVal * preAccNum + curSignal.Price * curSignal.Amount;
                    // 평균 단가를 구한다.
                    aveVal = priceSum / accNum;
                }
                else
                {
                    // 청산할 때는 평균단가는 변하지 않고 누적 개수만 바뀐다.
                    // 현재 청산 수익
                    double deltaPrice = (curSignal.Price - aveVal) * curSignal.Amount;
                    // 기존의 수익에 청산 수익을 더한다.
                    profitSum += deltaPrice;
                    // 누적 갯수는 현재 누적갯수에서 현재 청산 갯수를 뺀다.
                    accNum = accNum - curSignal.Amount;
                }
            }

            return profitSum;
        }

        /*
         * brief : 매도 신호에 대한 손익을 계산합니다.
         */
        private double CalcSellSignal()
        {
            double profitSum = 0.0;
            if (m_SellSignalManager.SignalList.Count < 2)
                return profitSum;
            // 누적 개수
            int accNum = 0;
            // 평균 단가
            double aveVal = 0.0;

            for (int i = 1; i < m_SellSignalManager.SignalList.Count; i++)
            {
                GTSignal.CSignal curSignal = m_SellSignalManager.SignalList[i];
                GTSignal.CSignal preSignal = m_SellSignalManager.SignalList[i - 1];
                if (curSignal.Type == GTSignalType.BUY)
                {
                    // 매도일 때는 아직 청산이 이루어지지 않았으므로 수익을 알 수 없다.
                    // 그러므로 평균 단가만 계산해 준다.
                    double preAveVal = aveVal;
                    int preAccNum = accNum;
                    accNum += curSignal.Amount;
                    double priceSum = preAveVal * preAccNum + curSignal.Price * curSignal.Amount;
                    // 평균 단가를 구한다.
                    aveVal = priceSum / accNum;
                }
                else
                {
                    // 청산할 때는 평균단가는 변하지 않고 누적 개수만 바뀐다.
                    // 현재 청산 수익
                    // 매도는 -가 되어야 수익이므로 앞에 마이너스를 붙여준다.
                    double deltaPrice = -(curSignal.Price - aveVal) * curSignal.Amount;
                    // 기존의 수익에 청산 수익을 더한다.
                    profitSum += deltaPrice;
                    // 누적 갯수는 현재 누적갯수에서 현재 청산 갯수를 뺀다.
                    accNum = accNum - curSignal.Amount;
                }
            }

            return profitSum;
        }
    }
}
